package com.ljt.yupictureddd.application.service.impl;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ljt.yupictureddd.application.service.PictureApplicationService;
import com.ljt.yupictureddd.application.service.SpaceAnalyzeApplicationService;
import com.ljt.yupictureddd.application.service.SpaceApplicationService;
import com.ljt.yupictureddd.application.service.UserApplicationService;
import com.ljt.yupictureddd.infrastracture.exception.BusinessException;
import com.ljt.yupictureddd.infrastracture.exception.ErrorCode;
import com.ljt.yupictureddd.infrastracture.exception.ThrowUtils;
import com.ljt.yupictureddd.infrastracture.mapper.SpaceMapper;
import com.ljt.yupictureddd.domain.picture.entity.Picture;
import com.ljt.yupictureddd.domain.space.entity.Space;
import com.ljt.yupictureddd.domain.user.entity.User;
import com.ljt.yupictureddd.interfaces.dto.space.analyze.*;
import com.ljt.yupictureddd.interfaces.vo.space.analyze.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class SpaceAnalyzeApplicationServiceImpl extends ServiceImpl<SpaceMapper, Space> implements SpaceAnalyzeApplicationService {
    private final UserApplicationService userApplicationService;
    private final SpaceApplicationService spaceApplicationService;
    private final PictureApplicationService pictureApplicationService;


    /**
     * 获得空间使用分析响应结果
     */
    @Override
    public SpaceUsageAnalyzeResponse getSpaceUsageAnalyzeResponse(SpaceUsageAnalyzeRequest spaceUsageAnalyzeRequest, User loginUser){
        // 校验参数
        Long spaceId = spaceUsageAnalyzeRequest.getSpaceId();
        boolean queryPublic = spaceUsageAnalyzeRequest.isQueryPublic();
        boolean queryAll = spaceUsageAnalyzeRequest.isQueryAll();
        // 校验权限
        this.checkSpaceAnalyzeAuth(spaceUsageAnalyzeRequest,loginUser);
        // 创建对象
        SpaceUsageAnalyzeResponse spaceUsageAnalyzeResponse = new SpaceUsageAnalyzeResponse();
        if (queryAll || queryPublic){
            // 查出对应的列,来后端处理
            QueryWrapper queryWrapper = new QueryWrapper<Picture>()
                    .select("picSize");
            // 填充lambdaquery
            fillAnalyzeQueryWrapper(spaceUsageAnalyzeRequest,queryWrapper);
            // 这里查出一列,不用list方法而是用selectObject只把列取出来,如果是list方法,则会查出picture对象..内存占用非常大 !!!细节
            List<Object> pictureSizeColumnList = spaceApplicationService.getBaseMapper().selectObjs(lambdaQuery());
            // 进行求和
            long pictureSizeSum = pictureSizeColumnList.stream().mapToLong(l -> (long) l)
                    .sum();
            // 填充参数
            // 如果是公共图库或者是全部空间的话
            // 最大空间最大数量.比例都应该为空
            spaceUsageAnalyzeResponse.setUsedSize(pictureSizeSum);
            spaceUsageAnalyzeResponse.setMaxSize(null);
            spaceUsageAnalyzeResponse.setSizeUsageRatio(null);
            spaceUsageAnalyzeResponse.setUsedCount(Long.valueOf(pictureSizeColumnList.size()));
            spaceUsageAnalyzeResponse.setMaxCount(null);
            spaceUsageAnalyzeResponse.setCountUsageRatio(null);
        } else {
            // 如果是个人空间的话,则不需要查询picture表,直接查询space空间表
            Space space = spaceApplicationService.getById(spaceId);
            Long maxSize = space.getMaxSize();
            Long maxCount = space.getMaxCount();
            Long totalSize = space.getTotalSize();
            Long totalCount = space.getTotalCount();

            spaceUsageAnalyzeResponse.setUsedSize(totalSize);
            spaceUsageAnalyzeResponse.setMaxSize(maxSize);
            spaceUsageAnalyzeResponse.setSizeUsageRatio(NumberUtil.round(totalSize * 100.0 / maxSize, 2).doubleValue());
            spaceUsageAnalyzeResponse.setUsedCount(totalCount);
            spaceUsageAnalyzeResponse.setMaxCount(maxCount);
            spaceUsageAnalyzeResponse.setCountUsageRatio(NumberUtil.round(totalCount * 100.0 / maxCount, 2).doubleValue());
        }
        return spaceUsageAnalyzeResponse;
    }

    /**
     * 获取空间图片分类分析
     */
    @Override
    public List<SpaceCategoryAnalyzeResponse> getSpaceCategoryAnalyze(SpaceCategoryAnalyzeRequest spaceCategoryAnalyzeRequest, User loginUser){
        // 校验参数
        Long spaceId = spaceCategoryAnalyzeRequest.getSpaceId();
        boolean queryPublic = spaceCategoryAnalyzeRequest.isQueryPublic();
        boolean queryAll = spaceCategoryAnalyzeRequest.isQueryAll();
        // 校验权限
        checkSpaceAnalyzeAuth(spaceCategoryAnalyzeRequest,loginUser);
        // 校验存在 - 》 无需
        // 业务逻辑 分析类的业务逻辑就是拼出sql
        // select category,count(*),sum(size) from picture where spaceId = xxx group by category
        // select category,count(*),sum(size) from picture where spaceId is null group by category
        // select category,count(*),sum(size) from picture group by category
        // where的拼接写好了通用方法直接调用
        // 这里告诉我们一个惨痛教训，聚合函数用queryWrapper
        // 1.准备wrapper
        QueryWrapper<Picture> pictureQueryWrapper = new QueryWrapper<>();
        pictureQueryWrapper.select("category","count(*)","sum(picSize)")
                .groupBy("category");
        // 2.给wrapper填充where条件
        fillAnalyzeQueryWrapper(spaceCategoryAnalyzeRequest,pictureQueryWrapper);
        // 3.查询，这里我们应该用的是selectMaps，不是用selectObjs，也不是list
        // 4.每一条都封装成一个Response
        return pictureApplicationService.getBaseMapper()
                .selectMaps(pictureQueryWrapper)
                .stream().map(map -> {
                    String category = (String) map.get("category");
                    Long count = (Long) map.get("count(*)");
                    Long totalSize = (Long) map.get("sum(picSize)");
                    return new SpaceCategoryAnalyzeResponse(category,count,totalSize);
                })
                .collect(Collectors.toList());
    }

    /**
     * 获取空间标签分析
     */
    @Override
    public List<SpaceTagAnalyzeResponse> getSpaceTagAnalyze(SpaceTagAnalyzeRequest spaceTagAnalyzeRequest, User loginUser){
        // 校验参数
        Long spaceId = spaceTagAnalyzeRequest.getSpaceId();
        boolean queryPublic = spaceTagAnalyzeRequest.isQueryPublic();
        boolean queryAll = spaceTagAnalyzeRequest.isQueryAll();
        // 校验权限
        checkSpaceAnalyzeAuth(spaceTagAnalyzeRequest,loginUser);
        // 校验存在
        // 业务逻辑
        // select tag from picture where spaceId = xxx;
        // select tag from picture where spaceId is null;
        // select tag from picture;
        // 1.创建wrapper
        QueryWrapper<Picture> pictureQueryWrapper = new QueryWrapper<>();
        pictureQueryWrapper.select("tags");
        // 2.填充where字段
        fillAnalyzeQueryWrapper(spaceTagAnalyzeRequest,pictureQueryWrapper);
        // 3.查询
        List<String> tagsJsonStrList = pictureApplicationService.getBaseMapper().selectObjs(pictureQueryWrapper);
        // 4.处理tag，对它计数
        // ["java","python"],["java","php"] = > "java","python","java","python" ，
        // 这些东西在一个数组里面这个叫做扁平化。多个数组 =》 一个数组就是扁平化，map别忘了stream继续用
        Map<String, Long> map = tagsJsonStrList.stream()
                .filter(ObjUtil::isNotNull)
                .flatMap(tagJson -> JSONUtil.toList(tagJson, String.class).stream())
                .collect(Collectors.groupingBy(tag -> tag, Collectors.counting()));
        // 转换为响应对象，按照使用次数排序
        return map.entrySet().stream()
                .sorted((e1, e2) -> Long.compare(e1.getValue(), e2.getValue()))
                .map(entry -> new SpaceTagAnalyzeResponse(entry.getKey(),entry.getValue()))
                .collect(Collectors.toList()) ;
    }

    /**
     * 获取空间图片大小分析
     */
    @Override
    public List<SpaceSizeAnalyzeResponse> getSpaceSizeAnalyze(SpaceSizeAnalyzeRequest spaceSizeAnalyzeRequest, User loginUser) {
        // 校验权限
        this.checkSpaceAnalyzeAuth(spaceSizeAnalyzeRequest, loginUser);
        // 创建对象
        // 构造wrapper
        QueryWrapper queryWrapper = new QueryWrapper<Picture>()
                .select("picSize");
        // 填充where语句
        fillAnalyzeQueryWrapper(spaceSizeAnalyzeRequest, queryWrapper);
        // 查询
        List<Object> objectList = spaceApplicationService.getBaseMapper().selectObjs(lambdaQuery());
        // 转换
        List<Long> picSizeList = objectList.stream()
                .filter(ObjUtil::isNotNull)
                .map(size -> (Long) size)
                .collect(Collectors.toList());
        // 定义分段范围，注意使用有序的Map
        LinkedHashMap<String, Long> map = new LinkedHashMap<>();
        map.put("<100KB",picSizeList.stream().filter(size -> size < 100 * 1024).count());
        map.put("100KB-500KB", picSizeList.stream().filter(size -> size >= 100 * 1024 && size <= 500 * 1024).count());
        map.put("500KB-1MB", picSizeList.stream().filter(size -> size >= 500 * 1024 && size <= 1 * 1024 * 1024).count());
        map.put(">1MB", picSizeList.stream().filter(size -> size > 1 * 1024 * 1024).count());
        // 转换为响应对象
        return map.entrySet().stream()
                .map(entry ->
                        new SpaceSizeAnalyzeResponse(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
    }

    /**
     * 获取空间用户上传行为分析
     */
    @Override
    public List<SpaceUserAnalyzeResponse> getSpaceUserAnalyze(SpaceUserAnalyzeRequest spaceUserAnalyzeRequest, User loginUser) {
        // 校验参数
        Long spaceId = spaceUserAnalyzeRequest.getSpaceId();
        boolean queryPublic = spaceUserAnalyzeRequest.isQueryPublic();
        boolean queryAll = spaceUserAnalyzeRequest.isQueryAll();
        Long userId = spaceUserAnalyzeRequest.getUserId();
        String timeDimension = spaceUserAnalyzeRequest.getTimeDimension();

        // 校验权限
        this.checkSpaceAnalyzeAuth(spaceUserAnalyzeRequest, loginUser);
        // 构造wrapper
        QueryWrapper queryWrapper = new QueryWrapper<Picture>();
        // 填充where条件
        fillAnalyzeQueryWrapper(spaceUserAnalyzeRequest, queryWrapper);
        // 还要根据userId来拼出sql
        if (ObjUtil.isNotNull(userId)) {
            queryWrapper.eq("userId", userId);
        }
        // 拼出时间sql
        switch (timeDimension) {
            case "day":
                queryWrapper.select("DATE_FORMAT(createTime,'%Y-%m-%d') as period","count (*) as count");
                break;
            case "week":
                queryWrapper.select("YEARWEEK(createTime') as period","count (*) as count");
                break;
            case "month":
                queryWrapper.select("DATE_FORMAT(createTime,'%Y-%m') as period","count (*) as count");
                break;
            default:
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "不支持的时间维度");
        }
        queryWrapper.groupBy("period");
        queryWrapper.orderByAsc("period");
        // 查询
        List<Map<String,Object>> queryResult = pictureApplicationService.getBaseMapper().selectMaps(queryWrapper);
        // 返回结果
        return queryResult.stream()
                .map(map -> {
                    String period = (String) map.get("period");
                    Long count = (Long) map.get("count");
                    return new SpaceUserAnalyzeResponse(period,count);
                })
                .collect(Collectors.toList());
    }

    /**
     * 获取空间使用排行分析
     */
    @Override
    public List<Space> getSpaceRankAnalyze(SpaceRankAnalyzeRequest spaceAnalyzeRequest, User loginUser) {
        Integer topN = spaceAnalyzeRequest.getTopN();
        Long spaceId = spaceAnalyzeRequest.getSpaceId();
        boolean queryPublic = spaceAnalyzeRequest.isQueryPublic();
        boolean queryAll = spaceAnalyzeRequest.isQueryAll();
        // 校验参数
        ThrowUtils.throwIf(ObjUtil.isNotNull(topN), ErrorCode.PARAMS_ERROR);
        // 校验权限
        // 不是管理员，抛出异常
        ThrowUtils.throwIf(!loginUser.isAdmin(), ErrorCode.NO_AUTH_ERROR);
        // 校验存在
        // 业务逻辑sql select * from space order by totalSize desc  limit 10;
        QueryWrapper<Space> spaceQueryWrapper = new QueryWrapper<>();
        spaceQueryWrapper.select("id", "spaceName", "userId", "totalSize")
                .orderByDesc("totalSize")
                .last("limit " + topN);
        List<Space> spaceList = spaceApplicationService.list(spaceQueryWrapper);
        return spaceList;
    }


    /**
     * 校验空间分析权限
     *
     * @param spaceAnalyzeRequest
     * @param loginUser
     */
    private void checkSpaceAnalyzeAuth(SpaceAnalyzeRequest spaceAnalyzeRequest, User loginUser) {
        Long spaceId = spaceAnalyzeRequest.getSpaceId();
        boolean queryPublic = spaceAnalyzeRequest.isQueryPublic();
        boolean queryAll = spaceAnalyzeRequest.isQueryAll();
        // 如果空间id传错,抛出异常
        ThrowUtils.throwIf(spaceId < 0, ErrorCode.PARAMS_ERROR);
        if (queryPublic || queryAll) {
            ThrowUtils.throwIf(!loginUser.isAdmin(), ErrorCode.NO_AUTH_ERROR);
        } else {
            ThrowUtils.throwIf(ObjUtil.isNull(spaceId), ErrorCode.PARAMS_ERROR);
            Space space = Optional.ofNullable(spaceApplicationService.getById(spaceId)).orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND_ERROR));
            spaceApplicationService.checkSpaceAuth(loginUser, space);
        }
    }

    /**
     * 通用方法-根据请求填充querywrapper的动态查询sql.其实就是拼了一个where spaceId = xxx 或者where spaceId is null
     * @param spaceAnalyzeRequest
     * @param queryWrapper
     */
    private void fillAnalyzeQueryWrapper(SpaceAnalyzeRequest spaceAnalyzeRequest,
                                                       QueryWrapper<Picture> queryWrapper) {
        Long spaceId = spaceAnalyzeRequest.getSpaceId();
        boolean queryPublic = spaceAnalyzeRequest.isQueryPublic();
        boolean queryAll = spaceAnalyzeRequest.isQueryAll();

        // 如果查询全部空间.select  sum (size ) from picture  group by null
        // 如果查询公共图库 select spaceId , sum (size ) from picture where spaceId is null group by spaceId
        // 如果用户自己的空间 select spaceId , sum (size ) from picture where spaceId is {xxxx} group by null
        if (queryAll) {
            return;
        } else if (queryPublic) {
            queryWrapper.isNull("spaceId");
            return;
        } else if (spaceId != null) {
            queryWrapper.eq("spaceId", spaceId);
            return;
        } else {
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"为指定查询范围");
        }
    }
}
